package com.example.demo.gateway.core.route;

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.refresh.NacosContextRefresher;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.example.demo.gateway.core.api.Api;
import com.example.demo.gateway.core.api.ApiManager;
import com.example.demo.gateway.core.consts.GatewayAttrTypeEnum;
import com.example.demo.utils.ReflectUtil;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * @Auther: miaoguoxin
 * @Date: 2020/3/18 10:01
 * @Description: 云端配置中心获取路由配置
 */
@Component
@Primary
@Slf4j
public class CloudConfigRouteLoader implements RouteLoaderInft {
    @Resource
    private RouteDefinitionWriter routeDefinitionWriter;
    @Value("${spring.application.name}")
    private String appName;
    @Value("${spring.profiles.active}")
    private String activeProfile;
    @Value("${spring.cloud.nacos.config.file-extension}")
    private String fileExtension;
    @Value("${spring.cloud.nacos.config.username}")
    private String username;
    @Value("${spring.cloud.nacos.config.password}")
    private String password;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private ApiManager apiManager;
    @Autowired
    private GatewayProperties gatewayProperties;
    @Autowired
    private ConfigService configService;

    @Bean
    ConfigService configService(NacosConfigProperties configProperties){
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.NAMESPACE, configProperties.getNamespace());
        properties.put(PropertyKeyConst.USERNAME,username);
        properties.put(PropertyKeyConst.PASSWORD,password);
        properties.put(PropertyKeyConst.SERVER_ADDR, configProperties.getServerAddr());
        ConfigService configService = null;
        try {
            configService = NacosFactory.createConfigService(properties);
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return configService;
    }


    @Override
    public void refreshApis() {

    }

    @Override
    public void refreshRoutes() {

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        String group = "DEFAULT_GROUP";
        this.loadRoutes(group);
       // this.loadApis(group);
    }

    private void loadApis(String group) throws NacosException {
        String apiConfigFile = this.getApiConfigFile();
        String apiFile = configService.getConfigAndSignListener(apiConfigFile, group, 5000, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                putApi(configInfo);
                //log.info("recieve:" + configInfo);
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });
        this.putApi(apiFile);
    }

    private void loadRoutes(String group) throws NacosException {
        String routeConfigFile = "route.properties";
        String routeConfig = configService.getConfigAndSignListener(routeConfigFile, group, 5000, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
               //refreshRoutes(configInfo);
                try {
                    Thread.sleep(5000);
                    List<RouteDefinition> routes = gatewayProperties.getRoutes();
                    routes.forEach(routeDefinition -> routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe());
                    applicationContext.publishEvent(new RefreshRoutesEvent(this));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });
        //this.refreshRoutes(routeConfig);
    }

    private void refreshRoutes(String routeConfig) {
        //开始装载spring cloud gateway 路由
        List<GatewayConfig> gatewayConfigs = JSON.parseArray(routeConfig, GatewayConfig.class);
        gatewayConfigs.forEach(this::loadRouteDefinition);
        applicationContext.publishEvent(new RefreshRoutesEvent(this));
    }

    private void putApi(String configInfo) {
        List<Api> apiList = JSON.parseArray(configInfo, Api.class);
        for (Api api : apiList) {
            apiManager.putConfig(api.getPatternUrl(), api);
        }
    }

    private String getRouteConfigFile(){
        return  String.format("%s-%s.%s",appName,activeProfile,fileExtension);
    }

    private String getApiConfigFile(){
        return String.format("%s-%s.%s",appName,"api","json");
    }


    private RouteDefinition loadRouteDefinition(GatewayConfig gatewayConfig) {

        RouteDefinition definition = new RouteDefinition();
        definition.setId(gatewayConfig.getServiceId());
        try {
            //必须要加上协议头才能访问
            URI uri;
            if (gatewayConfig.getUrl().startsWith("lb")) {
                uri = new URI(gatewayConfig.getUrl());
            } else {
                uri = UriComponentsBuilder.fromHttpUrl(gatewayConfig.getUrl()).build().toUri();
            }
            definition.setUri(uri);
        } catch (URISyntaxException e) {
            log.error("获取url异常", e);
        }
        //设置predicate和filter
        this.setPredicatesAndFilters(gatewayConfig, definition);
        routeDefinitionWriter.save(Mono.just(definition).map(r -> {
            r.setId(definition.getId());
            log.info("Saving route: " + definition);
            return r;
        })).subscribe(new Subscriber<Void>() {
            @Override
            public void onSubscribe(Subscription subscription) {
            }

            @Override
            public void onNext(Void aVoid) {
            }

            @Override
            public void onError(Throwable throwable) {

            }

            @Override
            public void onComplete() {
                log.info("完成操作");
            }
        });
        return definition;
    }
    private void setPredicatesAndFilters(GatewayConfig gatewayConfig, RouteDefinition definition) {
        List<GatewayAttrConfig> gatewayAttrConfigs = gatewayConfig.getAttrConfigs();
        if (gatewayAttrConfigs.isEmpty()) {
            throw new NullPointerException("gateway attrs can not allow empty please check");
        }
        List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
        List<FilterDefinition> filterDefinitions = new ArrayList<>();
        for (GatewayAttrConfig gatewayAttrConfig : gatewayAttrConfigs) {
            if (GatewayAttrTypeEnum.PREDICATE.getValue() == gatewayAttrConfig.getType()) {
                predicateDefinitions.add(this.getPredicateDefinition(gatewayAttrConfig));
            } else {
                filterDefinitions.add(this.getFilterDefinition(gatewayAttrConfig));
            }
        }
        definition.setPredicates(predicateDefinitions);
        definition.setFilters(filterDefinitions);
    }


    private PredicateDefinition getPredicateDefinition(GatewayAttrConfig gatewayAttrConfig) {
        PredicateDefinition predicateDefinition = new PredicateDefinition();
        predicateDefinition.setName(gatewayAttrConfig.getAttrName());
        if (!Strings.isNullOrEmpty(gatewayAttrConfig.getAttrArgs())) {
            Map<String, String> args = new LinkedHashMap<>();
            JSONObject jsonObject = JSON.parseObject(gatewayAttrConfig.getAttrArgs());
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                args.put(entry.getKey(),entry.getValue().toString());
            }
            predicateDefinition.setArgs(args);
        }
        return predicateDefinition;
    }


    private FilterDefinition getFilterDefinition(GatewayAttrConfig gatewayAttrConfig) {
        FilterDefinition filterDefinition = new FilterDefinition();
        filterDefinition.setName(gatewayAttrConfig.getAttrName());
        if (!Strings.isNullOrEmpty(gatewayAttrConfig.getAttrArgs())) {
            Map<String, String> args = new LinkedHashMap<>();
            JSONObject jsonObject = JSON.parseObject(gatewayAttrConfig.getAttrArgs());
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                args.put(entry.getKey(),entry.getValue().toString());
            }
            filterDefinition.setArgs(args);
        }
        return filterDefinition;
    }
}
